﻿using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication;
using System.Security.Claims;
using ZenSystemService.Api.Data;
using ZenSystemService.Api.Entities;
using Microsoft.EntityFrameworkCore;
using AutoMapper;
using ZenSystemService.Api.DTOs;
using ZenSystemService.Api.DTOs.Users;
using ZenSystemService.Api.Common.Auth;
using TencentCloud.Csip.V20221121.Models;
using Microsoft.IdentityModel.Tokens;
using ZenSystemService.Api.DTOs.Product;
using ZenSystemService.Api.DTOs.Auth;
using ZenSystemService.Api.Common.Encryption;

namespace ZenSystemService.Api.Services
{
    public class AuthService
    {
        private readonly ApplicationDbContext _context;
        private readonly IHttpContextAccessor _httpContextAccessor;
        private readonly IMapper _mapper;
        private readonly TokenService _tokenService;
        private readonly ProductService _productService;
        private readonly IEncryptionService _encryptionService;

        public AuthService(ApplicationDbContext context
            , IHttpContextAccessor httpContextAccessor
            , IMapper mapper
            , TokenService tokenService
            , ProductService productService,
            IEncryptionService encryptionService)
        {
            _context = context;
            _httpContextAccessor = httpContextAccessor;
            _mapper = mapper;
            _tokenService = tokenService;
            _productService = productService;
            _encryptionService = encryptionService;
        }

        public async Task<User> AuthenticateAsync(string username, string password)
        {
            var user = await _context.Users.SingleOrDefaultAsync(u => u.Username == username);

            if (user != null && VerifyPassword(password, user.PasswordHash, user.EncryptedPassword))
            {
                return user;
            }

            return null;
        }

        private bool VerifyPassword(string password, string hashPassword, string encryptedPassword)
        {
            if (!string.IsNullOrEmpty(password))
            {
                var encryptedResult = _encryptionService.Encrypt(password);
                if (string.Equals(encryptedResult, encryptedPassword))
                    return true;
                if (!string.IsNullOrEmpty(hashPassword))
                    return BCrypt.Net.BCrypt.Verify(password, hashPassword); 
            }
            return false;
        }

        public async Task<UserLoginResultDTO> AuthenticateAsync(LoginDTO login)
        {
            var user = await _context.Users
                .Include(u => u.UserRoles)
                .ThenInclude(ur => ur.Role)
                .Include(u => u.Institution)
                .SingleOrDefaultAsync(u => u.Username == login.Username);

            if (user != null && VerifyPassword(login.Password, user.PasswordHash, user.EncryptedPassword))
            {
                var loginResult = _mapper.Map<UserLoginResultDTO>(user);
                loginResult.Token = _tokenService.GenerateToken(user);
                return loginResult;
            }

            return null;
        }

        public async Task<ClientUserLoginResultDTO> AuthenticateClientAsync(ClientLoginRequestDTO login)
        {
            var user = await _context.Users
                .Include(u => u.UserRoles)
                .ThenInclude(ur => ur.Role)
                .Include(u => u.Institution)
                .FirstOrDefaultAsync(u => u.Username == login.Username);
            
            if (user != null && VerifyPassword(login.Password, user.PasswordHash, user.EncryptedPassword))
            {
                var userResult = _mapper.Map<UserLoginResultDTO>(user);

                var labeledProducts = await GetLabeledProductsAsync(user, login);
                
                userResult.Token = _tokenService.GenerateToken(user);
                //await UpdateProductStatusAsync(labeledProducts);
                return new ClientUserLoginResultDTO
                {
                    User = userResult,
                    Products = labeledProducts
                };
            }

            return null;
        }

        public async Task<IEnumerable<ProductUniqueNumberDTO>> GetLabeledProductsAsync(User admin, ClientLoginRequestDTO login)
        {
            var result = new List<ProductUniqueNumberDTO>();
            var products = await _productService.LabelProductsAsync(admin, login);

            foreach (var product in products)
            {
                result.Add(new ProductUniqueNumberDTO
                {
                    ProductId = product.ProductId,
                    ProductName = product.Name,
                    UniqueNumber = product.UniqueNumber,
                    ProductTypeId = product.ProductId,
                    ProductTypeName = product.ProductType.Name,
                    MacAddress = product.MacAddress,
                    Alias = product.Alias
                });
            }
            return result;
        }

        public async Task<User> GetUserByUsernameAsync(string username)
        {
            return await _context.Users.FirstOrDefaultAsync(u => u.Username == username);
        }

        public async Task SignInAsync(UserDTO user, bool rememberMe)
        {
            var claims = new List<Claim>
            {
                new Claim(ClaimTypes.Name, user.Username),
                new Claim(ClaimTypes.NameIdentifier, user.UserId.ToString())
            };

            //foreach (var userRole in user.UserRoles)
            //{
            //    claims.Add(new Claim(ClaimTypes.Role, userRole.Role.Name));
            //}

            var claimsIdentity = new ClaimsIdentity(
                claims, CookieAuthenticationDefaults.AuthenticationScheme);

            var authProperties = new AuthenticationProperties
            {
                IsPersistent = rememberMe
            };

            await _httpContextAccessor.HttpContext.SignInAsync(
                CookieAuthenticationDefaults.AuthenticationScheme,
                new ClaimsPrincipal(claimsIdentity),
                authProperties);
        }

        public async Task SignOutAsync()
        {
            await _httpContextAccessor.HttpContext.SignOutAsync(
                CookieAuthenticationDefaults.AuthenticationScheme);
        }

        public async Task<bool> IsUserInRoleAsync(int userId, string roleName)
        {
            return await _context.UserRoles
                .AnyAsync(ur => ur.UserId == userId && ur.Role.Name == roleName);
        }

        public async Task<bool> UserHasAccessToFunctionAsync(string userName, string functionName)
        {
            var query = from u in _context.Users
                        join ur in _context.UserRoles
                        on u.UserId equals ur.UserId
                        join r in _context.Roles
                        on ur.RoleId equals r.Id
                        join rf in _context.RoleFunctions
                        on r.Id equals rf.RoleId
                        join f in _context.BusinessFunctions
                        on rf.FunctionId equals f.Id
                        where u.Username == userName && f.Name == functionName
                        select u;
            return query.Any();
        }
    }
}